use ahash::AHashMap;
use godot::{classes::ResourceLoader, prelude::*};

#[derive(Default)]
struct RegistryNode {
	resources: AHashMap<StringName, Gd<Resource>>,
	children: AHashMap<StringName, Box<Self>>,
}

impl RegistryNode {
	fn clear(&mut self) {
		self.resources.clear();
		self.children.clear();
	}

    fn register_dir(&mut self, mut loader: Gd<ResourceLoader>, res_dir_path: GString, recursive: bool) {
        let sep = GString::from("/");
        for entry in loader.list_directory(&res_dir_path).as_slice().iter() {
            if entry.ends_with(&sep) && recursive {
                let mut child = Self::default();
                child.register_dir(loader.clone(), res_dir_path.path_join(entry), recursive);
                self.children.insert(entry.get_base_dir().into(), Box::new(child));
            } else if let Some(res) = loader.load(&res_dir_path.path_join(entry)) {
                self.resources.insert(entry.get_basename().into(), res);
            }
        }
    }

    fn register(&mut self, path: NodePath, res: Gd<Resource>) {
        let len = path.get_name_count();
        if len == 1 {
            self.resources.insert(path.get_name(0), res);
        } else if len > 1 {
            self.children
                .entry(path.get_name(0))
                .or_default()
                .register(path.subpath(1, i32::MAX), res);
        }
    }

    fn get_res(&self, path: &NodePath) -> Option<Gd<Resource>> {
        let len = path.get_name_count();
        if len == 1 {
            self.resources.get(&path.get_name(0)).cloned()
        } else if len > 1 {
            self.children
                .get(&path.get_name(0))
                .and_then(|child| child.get_res(&path.subpath(1, i32::MAX)))
        } else {
            None
        }
    }

    fn at(&self, path: &NodePath) -> Variant {
        let res = self.get_res(path);
        fn at_iter(any: Variant, path: &NodePath, idx: usize) -> Variant {
            if idx >= path.get_subname_count() {
                return any
            }

            if let Ok(obj) = any.try_to::<Gd<Object>>() {
                return at_iter(obj.get(&path.get_subname(idx)), path, idx + 1)
            }

            any
        }

        at_iter(res.to_variant(), path, 0)
    }
	
    fn ids(&self, path: &NodePath) -> Array<StringName> {
        if path.get_name_count() == 0 {
            self.resources.keys().cloned().collect()
        } else {
            self.children
                .get(&path.get_name(0))
                .map(|child| child.ids(&path.subpath(1, i32::MAX)))
                .unwrap_or_default()
        }
    }

	
    fn child_names(&self, path: &NodePath) -> Array<StringName> {
		if path.get_name_count() == 0 {
            self.children.keys().cloned().collect()
        } else {
            self.children
                .get(&path.get_name(0))
                .map(|child| child.child_names(&path.subpath(1, i32::MAX)))
                .unwrap_or_default()
        }
    }
}

#[derive(GodotClass)]
#[class(init)]
pub struct ResourceRegistry {
    root: RegistryNode,
}

#[godot_api]
impl ResourceRegistry {
    #[func]
    pub fn clear(&mut self) {
        self.root.clear();
    }

    #[func]
    pub fn register_dir(&mut self, res_dir_path: GString, recursive: bool) {
		self.root.register_dir(ResourceLoader::singleton(), res_dir_path, recursive)
    }

    #[func]
    pub fn register(&mut self, path: NodePath, res: Gd<Resource>) {
        self.root.register(path, res)
    }

    #[func]
    pub fn get_res(&self, path: NodePath) -> Option<Gd<Resource>> {
        self.root.get_res(&path)
	}

    #[func]
    pub fn at(&self, path: NodePath) -> Variant {
        self.root.at(&path)
    }

    #[func]
    pub fn ids(&self, path: NodePath) -> Array<StringName> {
        self.root.ids(&path)
    }

    #[func]
    pub fn child_names(&self, path: NodePath) -> Array<StringName> {
		self.root.child_names(&path)
    }
}